在閱讀 Apple Education 出版的 Develop in Swift Data Collections 這本書時,關於 URLSession 部分,注意到在 Swift 5.5 之後引入新的 async / await API,因此想簡單做個筆記。而本文主要目的是比較新舊不同的實作方式,對於錯誤處理就先簡單處理。如果有理解錯誤,還煩請指正,謝謝。內容主要改編書中提到的例子。
enum PhotoInfoError: Error, LocalizedError {
case itemNotFound
}
struct PhotoInfo: Codable {
var title: String
var description: String
var url: URL
var copyright: String?
enum CodingKeys: String, CodingKey {
case title
case description = "explanation"
case url
case copyright
}
}
func fetchPhotoInfoNew() async throws -> PhotoInfo {
let url = URL(string: "https://api.nasa.gov/planetary/apod?api_key=DEMO_KEY")!
let session = URLSession(configuration: .default)
let (data, response) = try await session.data(from: url)
guard let httpResponse = response as? HTTPURLResponse,
httpResponse.statusCode == 200 else {
throw PhotoInfoError.itemNotFound
}
let jsonDecoder = JSONDecoder()
let photoInfo = try jsonDecoder.decode(PhotoInfo.self, from: data)
return(photoInfo)
}
Task {
do {
let photoInfo = try await fetchPhotoInfoNew()
print("Successfully fetched PhotoInfo: \(photoInfo)")
} catch {
print("Fetch PhotoInfo failed with error: \(error)")
}
}
用 async 關鍵字宣告 fetchPhotoInfoNew()
為非同步函式,並使用 await 關鍵字執行 URLSession 的 data(from:)
方法獲取 URL 的回應。當回應的 HTTP 狀態碼為 200 時,會將數據解碼為 PhotoInfo 並返回該實例。Task 這個實例則用於執行非同步函數 fetchPhotoInfoNew()
,印出 photoInfo 的值或處理錯誤。
而在這之前的作法為使用 URLSession.dataTask(with: URLRequest, completionHandler: (Data?, URLResponse?, Error?) -> Void) { ... }
當 data task 完成時,在 completionHandler
這個 closure 裡處理 response、data,或者 error 的狀況。也就是說,這個 closure 是當 task 完成時會被自動呼叫,並透過這個 closure 回傳結果,以便開發者進一步處理。程式碼如下:
URLSession.dataTask
func fetchPhotoInfoOld() {
if let url = URL(string: "https://api.nasa.gov/planetary/apod?api_key=DEMO_KEY") {
let session = URLSession(configuration: .default)
let task = session.dataTask(with: url) { data, response, error in
guard let httpResponse = response as? HTTPURLResponse,
httpResponse.statusCode == 200 else {
return
}
guard let safeData = data else {
print("No data")
return
}
do {
let jsonDecoder = JSONDecoder()
let photoInfo = try jsonDecoder.decode(PhotoInfo.self, from: safeData)
print("Successfully fetched PhotoInfo: \(photoInfo)")
} catch {
print("Failed to decode PhotoInfo: \(error)")
}
}
task.resume()
}
}
fetchPhotoInfoOld()
使用 async / await 的好處是避免使用那一串 closure,讓程式碼變得較為簡潔。